/** @file
@brief Provides the boolean pin type, and functionality and components for dealing with it. */
#include <pryn.h>

#define PrynBooleanTypeId "Pryn.Boolean"

#define booleanColorFalseGray prynColorGray (175)
#define booleanColorFalse prynColorBlack
#define booleanColorTrueGray prynColorRGB (200, 150, 150)
#define booleanColorTrue prynColorRGB (200, 255, 200)
#define booleanPinColorFalseInner prynColorRGB (255, 230, 230)
#define booleanPinColorTrueInner prynColorRGB (230, 255, 230)
#define booleanPinColorFalseOuter prynColorRGB (255, 128, 128)
#define booleanPinColorTrueOuter prynColorRGB (128, 255, 128)

static void setBooleanPinValue (PrynPin *pin, bool value)
{
	PrynValue *pinValue;
	
	if (!pin) return;
	pinValue = pin->value ();

	if (pinValue && pinValue->type ()->id () == PrynBooleanTypeId)
	{
		*(bool *) pinValue->pointer () = value;
		pin->changeValue ();
	}
	else
		pin->value (PrynValue::Create (pin->state (), PrynBooleanTypeId, &value));
}

static bool getBooleanPinValue (PrynPin *pin, bool defaultValue = false)
{
	PrynValue *value;
	return (pin && (value = pin->value ()) != 0) ? *(bool *) value->pointer () : defaultValue;
}

class PrynBooleanType : PrynTypeClass
{
public:
	PrynBooleanType (PrynLibrary *library) { construct (library); }
	PrynString id () const { return "Pryn.Boolean"; }
	PrynString defaultName () { return "Boolean"; }
	PrynString defaultDescription () { return "A value that can be true or false."; }

	static void BooleanConnectionImage (PrynColor black, PrynColor white, PrynColor other, int width, int height, PrynColor *image, PrynColor &fallback)
	{
		fallback = other;
		
		if (image)
		{
			for (int y = 0; y < height; y ++)
			for (int x = 0; x < width; x ++)
			{
				if (width > 8)
				{
					int o = y * 2 - prynAbsolute (x - width / 2) * height / width;
					image [x + y * width] = (o < height && o > 0) ? white : black;
				}
				else
					image [x + y * width] = (y * 2 > height) ? white : black;
			}
		}
	}

protected:
	virtual PrynValue *createFunction (void *initial)
	{
		return allocateValue (sizeof (bool), initial);
	}

	virtual void connectionImageFunction (PrynValue *value, int width, int height, PrynColor *image, PrynColor &fallback)
	{
		bool set = *(bool *) value->pointer ();

		BooleanConnectionImage (set ? booleanColorFalseGray : booleanColorFalse, set ? booleanColorTrue : booleanColorTrueGray, set ? booleanColorTrue : booleanColorFalse, width, height, image, fallback);
	}

	virtual void colorsFunction (PrynValue *value, PrynColor &outer, PrynColor &inner)
	{
		bool set = *(bool *) value->pointer ();
		inner = set ? booleanPinColorTrueInner : booleanPinColorFalseInner;
		outer = set ? booleanPinColorTrueOuter : booleanPinColorFalseOuter;
	}
};

class PrynBooleanToggleFactory : public PrynFactoryClass
{
public:
	PrynBooleanToggleFactory (PrynLibrary *library) { construct (library); }

	PrynString id () const { return "Pryn.Boolean.Toggle"; }
	PrynString defaultName () { return "Boolean toggle"; }
	PrynString defaultDescription () { return "Outputs either boolean true or false."; }

protected:
	PrynComponent *createFunction ()
	{
		PrynString id, name, description;
		PrynPin *pin = 0;
		PrynComponent *component = 0;

		try
		{
			component = allocateComponent ();
			pin = component->createOutputPin ("output", "Output", "The boolean output value from the toggle.");
			setBooleanPinValue (pin, true);
		}
		catch (PrynException &exception)
		{
			component->destroy ();
			throw exception;
		}

		return component;
	}

	PrynString displayNameFunction (PrynComponent *component)
	{
		PrynValue *value = component->firstOutputPin ()->value ();
		return (value && *(bool *) value->pointer ()) ? "True" : "False";
	}

	void clickFunction (PrynComponent *component)
	{
		PrynPin *pin = component->firstOutputPin ();
		setBooleanPinValue (pin, !getBooleanPinValue (pin));
	}
};

/// This is the root for any boolean input->boolean output component factory. It adds a boolean input and output pin to any allocated component. If the last input pin is connected, another input pin is created.
class PrynBooleanOperationFactory : public PrynFactoryClass
{
public:
	/// In addition to superclass behaviour, this adds a boolean input and output pin to the component.
	PrynComponent *allocateComponent (size_t proprietarySize = 0)
	{
		PrynComponent *component = 0;
		
		try
		{
			component = PrynFactoryClass::allocateComponent (proprietarySize);
			component->createInputPin (0, "Input", "Boolean input value.");
			component->createOutputPin (0, "Output", "The boolean output value from the logical OR.");
		}
		catch (PrynException &exception)
		{
			component->destroy ();
			throw exception;
		}

		return component;
	}

	/// Return the value of the component based on its input pins. This is used in #pinChangeValueFunction to assign the proper value.
	virtual bool value (PrynComponent *component) = 0;

protected:
	/// Merely allocate the component, which comes with input and output pins.
	virtual PrynComponent *createFunction ()
	{
		return allocateComponent ();
	}

	/// Set the value of the output pin based upon the result of #value.
	virtual void pinChangeValueFunction (PrynPin *pin)
	{
		PrynComponent *component = 0;

		if (pin->isOutput ()) return;
		component = pin->component ();
		setBooleanPinValue (component->firstOutputPin (), value (component));
	}

	/// If it's the last input pin that's been connected, append another input pin to the end.
	virtual void pinConnectedFunction (PrynPin *pin)
	{
		if (pin->isLast () && pin->isInput ())
			pin->component ()->createInputPin ("input", "Input", "Boolean input value.");
	}
};

class PrynBooleanOrFactory : public PrynBooleanOperationFactory
{
public:
	PrynBooleanOrFactory (PrynLibrary *library) { construct (library); }
	PrynString id () const { return "Pryn.Boolean.Or"; }
	virtual PrynString defaultName () { return "Logical OR"; }
	virtual PrynString defaultDescription () { return "Outputs true if any of the inputs are true, or false otherwise."; }

	bool value (PrynComponent *component)
	{
		for (PrynPin *input = 0; component->iterateInputPins (&input); )
			if (getBooleanPinValue (input))
				return true;
		return false;
	}
};

class PrynBooleanAndFactory : public PrynBooleanOperationFactory
{
public:
	PrynBooleanAndFactory (PrynLibrary *library) { construct (library); }
	PrynString id () const { return "Pryn.Boolean.And"; }
	virtual PrynString defaultName () { return "Logical AND"; }
	virtual PrynString defaultDescription () { return "Outputs true if all of the inputs are true, or false otherwise."; }

	bool value (PrynComponent *component)
	{
		for (PrynPin *input = 0; component->iterateInputPins (&input); )
			if (!getBooleanPinValue (input, true))
				return false;
		return true;
	}
};

class PrynBooleanXorFactory : public PrynBooleanOperationFactory
{
public:
	PrynBooleanXorFactory (PrynLibrary *library) { construct (library); }
	PrynString id () const { return "Pryn.Boolean.Xor"; }
	virtual PrynString defaultName () { return "Logical exclusive OR"; }
	virtual PrynString defaultDescription () { return "Outputs true if one and only one input is true, or false otherwise."; }

	bool value (PrynComponent *component)
	{
		bool result = false;

		for (PrynPin *input = 0; component->iterateInputPins (&input); )
			if (getBooleanPinValue (input, false))
			{
				if (result) return false;
				result = true;
			}
		return result;
	}
};

PrynLibraryEntryPoint ()
{
	library->name ("Boolean type and components");
	library->description ("This provides the boolean type and logic components like logical 'and'.");
	
	new PrynBooleanType (library);
	new PrynBooleanToggleFactory (library);
	new PrynBooleanOrFactory (library);
	new PrynBooleanAndFactory (library);
	new PrynBooleanXorFactory (library);
}
PrynLibraryEntryPointEnd ()